package cn.edu.sjtu.stap.cg;

import java.util.*;

import org.aspectj.ajdt.internal.compiler.ast.*;
import org.aspectj.org.eclipse.jdt.internal.compiler.ast.*;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.*;

import cn.edu.sjtu.stap.graph.*;
import cn.edu.sjtu.stap.hg.*;
import cn.edu.sjtu.stap.util.*;

public class CallGraph extends Graph {
    private static final long serialVersionUID = 1L;

    // private static Set<TypeDeclaration> knownTypes = new
    // HashSet<TypeDeclaration>();

    private CgMethod root;

    protected CallGraph() {
        // Empty.
    }

    public CallGraph(AbstractMethodDeclaration method) {
        initializeRoot(method);

        this.add(root);
    }

    private void initializeRoot(AbstractMethodDeclaration method) {
        if (method instanceof AdviceDeclaration) {
            root = new CgAdvice();
        } else {
            root = new CgMethod();
        }
        root.setASTNode(method);
    }

    public CallGraph(CgMethod method) {
        if (null == method) {
            throw new NullPointerException("Root cannot be null!");
        }
        root = method;
    }

    // public static void addKnownTypes(Collection<TypeDeclaration> types) {
    // knownTypes.addAll(types);
    // }
    //
    // public static Set<TypeDeclaration> getKnownTypes() {
    // return knownTypes;
    // }
    //
    // public static void clearKnownTypes() {
    // knownTypes.clear();
    // }

    public static CallGraph getSubCallGraph(CgMethod node) {
        if (null == node) {
            throw new NullPointerException();
        }

        // Make a new call graph.
        CallGraph newCg = new CallGraph(node);
        Stack<CgNode> stack = new Stack<CgNode>();
        GraphNodeSet visited = new GraphNodeSet();

        stack.push(node);
        while (!stack.isEmpty()) {
            CgNode n = stack.pop();

            // If the node has not been visited, add it to the new call graph.
            if (!visited.contains(n)) {
                newCg.add(n);
                visited.add(n);

                Iterator<GraphEdge> i = n.getOutgoingEdgeSet().iterator();
                while (i.hasNext()) {
                    GraphEdge edge = i.next();
                    newCg.add(edge);
                    CgNode c = (CgNode) (edge.getDestinationNode());
                    stack.push(c);
                }
            }
        }

        return newCg;
    }

    public CgMethod getRoot() {
        return root;
    }

    public CgMethod find(AbstractMethodDeclaration m) {
        Iterator<GraphNode> i = this.getNodes().iterator();
        while (i.hasNext()) {
            CgNode node = (CgNode) i.next();
            if (node.getASTNode() == m) {
                return (CgMethod) node;
            }
        }

        return null;
    }

    public CgAdvice find(AdviceDeclaration a) {
        Iterator<GraphNode> i = this.getNodes().iterator();
        while (i.hasNext()) {
            CgNode node = (CgNode) i.next();
            if (node.getASTNode() == a) {
                return (CgAdvice) node;
            }
        }

        return null;
    }

    public List<CgCallSite> find(InvocationSite s) {
        CallMap map = CallMap.getInstance();
        AbstractMethodDeclaration caller = null;
        AbstractMethodDeclaration callee = null;

        if (s instanceof MessageSend) {
            MessageSend ms = (MessageSend) s;
            caller = map.findCaller(ms);
            callee = HierarchyGraph.getStaticCallee(ms);
        } else if (s instanceof AllocationExpression) {
            AllocationExpression ae = (AllocationExpression) s;
            caller = map.findCaller(ae);
            callee = HierarchyGraph.getStaticCallee(ae);
        } else if (s instanceof ExplicitConstructorCall) {
            ExplicitConstructorCall ecc = (ExplicitConstructorCall) s;
            caller = map.findCaller(ecc);
            callee = HierarchyGraph.getStaticCallee(ecc);
        }

        if (caller == null || callee == null) {
            return null;
        } else {
            List<CgCallSite> result = new ArrayList<CgCallSite>();
            try {
                List<CgMethod> callees = findOverridingCallees(caller, callee);
                Iterator<CgMethod> i = callees.iterator();
                while (i.hasNext()) {
                    AbstractMethodDeclaration m = (AbstractMethodDeclaration) i.next().getASTNode();
                    CgCallSite cs = find(caller, m);
                    if (null != cs) {
                        result.add(cs);
                    }
                }
            } catch (MethodNotFoundException e) {
                // Skip quietly.
            }

            return result;
        }
    }

    public CgCallSite find(AbstractMethodDeclaration caller, AbstractMethodDeclaration callee) {
        Iterator<GraphNode> i = this.getNodes().iterator();
        while (i.hasNext()) {
            CgNode node = (CgNode) i.next();
            if (node instanceof CgCallSite) {
                CgCallSite cs = (CgCallSite) node;
                if (cs.getCaller().getASTNode() == caller && cs.getCallee().getASTNode() == callee) {
                    return cs;
                }
            }
        }

        return null;
    }

    public List<CgMethod> findCallers(CgMethod method) {
        if (null == method) {
            throw new NullPointerException();
        }

        ArrayList<CgMethod> callers = new ArrayList<CgMethod>();
        Iterator<GraphNode> i = method.getSourceNodeSet().iterator();
        while (i.hasNext()) {
            GraphNode node = i.next();
            CgCallSite cs = (CgCallSite) node;
            CgMethod caller = (CgMethod) cs.getSourceNodeSet().iterator().next();
            callers.add(caller);
        }

        return callers;
    }

    public List<CgMethod> findCallers(AbstractMethodDeclaration method) throws MethodNotFoundException {
        CgMethod m = find(method);
        if (null == m) {
            throw new MethodNotFoundException();
        }

        return findCallers(m);
    }

    public List<CgMethod> findCallees(CgMethod method) {
        if (null == method) {
            throw new NullPointerException();
        }

        ArrayList<CgMethod> callees = new ArrayList<CgMethod>();
        Iterator<GraphNode> i = method.getDestinationNodeSet().iterator();
        while (i.hasNext()) {
            GraphNode node = i.next();
            if (node instanceof CgCallSite) {
                CgCallSite cs = (CgCallSite) node;
                CgMethod callee = (CgMethod) cs.getDestinationNodeSet().iterator().next();
                callees.add(callee);
            }
        }

        return callees;
    }

    public List<CgMethod> findCallees(AbstractMethodDeclaration method) throws MethodNotFoundException {
        CgMethod m = find(method);
        if (null == m) {
            throw new MethodNotFoundException();
        }

        return findCallees(m);
    }

    public List<CgMethod> findOverridingCallees(AbstractMethodDeclaration caller, AbstractMethodDeclaration callee)
            throws MethodNotFoundException {
        List<CgMethod> result = new ArrayList<CgMethod>();
        List<CgMethod> possibleCallees = findCallees(caller);
        Iterator<CgMethod> pIt = possibleCallees.iterator();
        while (pIt.hasNext()) {
            CgMethod node = pIt.next();
            AbstractMethodDeclaration possibleCallee = (AbstractMethodDeclaration) node.getASTNode();
            if (HierarchyGraph.compareMethod(callee, possibleCallee)) {
                result.add(node);
            }
        }

        return result;
    }

    public List<CgAdvice> findAdvices(CgMethod method) {
        if (null == method) {
            throw new NullPointerException();
        }

        ArrayList<CgAdvice> advices = new ArrayList<CgAdvice>();
        Iterator<GraphNode> i = method.getDestinationNodeSet().iterator();
        while (i.hasNext()) {
            GraphNode n = i.next();
            if (n instanceof CgAdvice) {
                advices.add((CgAdvice) n);
            }
        }

        // Sort these advices.
        Collections.sort(advices, new CgAdviceComparator());

        return advices;
    }

    public List<CgAdvice> findAdvices(AbstractMethodDeclaration method) throws MethodNotFoundException {
        CgMethod m = find(method);
        if (null == m) {
            throw new MethodNotFoundException();
        }

        return findAdvices(m);
    }

    public List<CgAdvice> findAdvices(CgCallSite cs) {
        if (null == cs) {
            throw new NullPointerException();
        }

        ArrayList<CgAdvice> advices = new ArrayList<CgAdvice>();
        Iterator<GraphNode> i = cs.getDestinationNodeSet().iterator();
        while (i.hasNext()) {
            GraphNode n = i.next();
            if (n instanceof CgAdvice) {
                advices.add((CgAdvice) n);
            }
        }

        // Sort these advices.
        Collections.sort(advices, new CgAdviceComparator());

        return advices;
    }

    public List<CgAdvice> findAdvices(InvocationSite cs) throws CallSiteNotFoundException {
        List<CgCallSite> c = find(cs);
        if (null == c) {
            throw new CallSiteNotFoundException();
        }

        Set<CgAdvice> r = new HashSet<CgAdvice>();
        Iterator<CgCallSite> i = c.iterator();
        while (i.hasNext()) {
            CgCallSite callSite = i.next();
            r.addAll(findAdvices(callSite));
        }

        List<CgAdvice> result = new ArrayList<CgAdvice>();
        result.addAll(r);

        // Sort these advices.
        Collections.sort(result, new CgAdviceComparator());

        return result;
    }

    public List<ICgShadow> findShadows(CgAdvice advice) {
        if (null == advice) {
            throw new NullPointerException();
        }

        ArrayList<ICgShadow> advicees = new ArrayList<ICgShadow>();
        Iterator<GraphNode> i = advice.getSourceNodeSet().iterator();
        while (i.hasNext()) {
            advicees.add((ICgShadow) i.next());
        }

        return advicees;
    }

    public List<ICgShadow> findShadows(AdviceDeclaration advice) throws AdviceNotFoundException {
        CgAdvice a = find(advice);
        if (null == a) {
            throw new AdviceNotFoundException();
        }

        return findShadows(a);
    }

    private class CgAdviceComparator implements Comparator<CgAdvice> {
        public int compare(CgAdvice o1, CgAdvice o2) {
            AdviceDeclaration a1 = (AdviceDeclaration) o1.getASTNode();
            AdviceDeclaration a2 = (AdviceDeclaration) o2.getASTNode();

            return Utils.comparePrecedence(a1, a2);
        }
    }
}
